﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Windows.Media;
using System.Windows;
using System.Threading;

using Faust.Andrew.MTG.Helper;
using Shared = Faust.Andrew.MTG.Player.Shared;

namespace Faust.Andrew.MTG.Player
{
    public class GameModel : INotifyPropertyChanged
    {
        #region fields        
        private GamePresenter _presenter;
        private bool _ignoreCardPropertyChange = false;
        private bool _ignorePlayerPropertyChange = false;        

        protected Dictionary<string, ImageSource> _tokenImages;
        public Dictionary<string, ImageSource> TokenImages
        {
            get
            {
                return _tokenImages;
            }
        }

        private ObservableCollection<Token> _tokens;
        public ObservableCollection<Token> Tokens
        {
            get { return _tokens; }
            set { _tokens = value; }
        }
        
        private bool _isUsingPlanes = false;
        public bool IsUsingPlanes
        {
            get { return _isUsingPlanes; }
            set
            {
                if (_isUsingPlanes != value)
                {
                    _isUsingPlanes = value;
                    FirePropertyChanged("IsUsingPlanes");
                }
            }
        }
        

        #region Cards
        DataStore _dataStore;
        Gatherer _gatherer;
        #endregion

        #region Communications
        private ServiceHost _serviceHost;
        private MTGServer _server;
        private Client _client;
        private bool _serverRunning;
        #endregion

        //#region Game
        //private Shared.GameState _gameState;
        //#endregion
        #endregion

        #region Properties
        private bool _isHosting;
        public bool IsHosting
        {
            get { return _isHosting; }
            set
            {
                if (_isHosting != value)
                {
                    if (_isHosting != value)
                    {
                        _isHosting = value;
                        FirePropertyChanged("IsHosting");
                    }
                }
            }
        }

        private bool _isInGame;
        public bool IsInGame
        {
            get { return _isInGame; }
            set
            {
                if (_isInGame != value)
                {
                    _isInGame = value;
                    FirePropertyChanged("IsInGame");
                }
            }
        }

        private bool _isGameStarted;
        public bool IsGameStarted
        {
            get { return _isGameStarted; }
            set
            {
                if (_isGameStarted != value)
                {
                    _isGameStarted = value;
                    FirePropertyChanged("IsGameStarted");
                }
            }
        }


        private string _userDirectory;
        public string UserDirectory
        {
            get
            {
                return _userDirectory;
            }
        }

        private ObservableCollection<Shared.GameCard> _hand;
        public ObservableCollection<Shared.GameCard> Hand
        {
            get { return _hand; }
            set
            {
                if (_hand != value)
                {
                    _hand = value;
                    FirePropertyChanged("Hand");
                }
            }
        }
        public ObservableCollection<Shared.Notification> Notifications { get; set; }

        private ObservableCollection<Shared.GameCard> _workspace;
        public ObservableCollection<Shared.GameCard> Workspace
        {
            get { return _workspace; }
            set
            {
                if (_workspace != value)
                {
                    _workspace = value;
                    FirePropertyChanged("Workspace");
                }
            }
        }

        private ObservableCollection<Shared.GameCard> _graveYard;
        public ObservableCollection<Shared.GameCard> GraveYard
        {
            get { return _graveYard; }
            set
            {
                if (_graveYard != value)
                {
                    _graveYard = value;
                    FirePropertyChanged("GraveYard");
                }
            }
        }
        

        private PlayerState _currentPlayer;
        public PlayerState CurrentPlayer
        {
            get { return _currentPlayer; }
            set
            {
                if (_currentPlayer != value)
                {
                    _currentPlayer = value;
                    FirePropertyChanged("CurrentPlayer");
                }
            }
        }

        private Dictionary<int, PlayerState> _playerDictionary;
        private Dictionary<int, Shared.GameCard> _allCards;
        private ObservableCollection<PlayerState> _players;
        public ObservableCollection<PlayerState> Players
        {
            get { return _players; }
            set
            {
                if (_players != value)
                {
                    _players = value;
                    FirePropertyChanged("Players");
                }
            }
        }

        public double LargestZIndex(int playerID)
        {
            if (_playerDictionary[playerID].Cards.Count == 0) { return 0; }

            return (from card in _playerDictionary[playerID].Cards
                          where card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.InPlay
                          select card.Location.Z).Max();
        }

        public PlayerState PlayerByName(string playerName)
        {
            return (from player in _players
                    where player.Name.Equals(playerName, StringComparison.CurrentCultureIgnoreCase)
                    select player).Single();
        }
        public PlayerState PlayerByID(int playerID)
        {
            return _playerDictionary[playerID];
        }
        public bool IsCurrentPlayer(int playerID)
        {
            return (playerID == CurrentPlayer.PlayerID);
        }

        private ObservableCollection<Shared.Counter> _additionCounters;
        public ObservableCollection<Shared.Counter> AdditionCounters
        {
            get { return _additionCounters; }
            set { _additionCounters = value; }
        }

        private ObservableCollection<Shared.Counter> _subtractionCounters;
        public ObservableCollection<Shared.Counter> SubtractionCounters
        {
            get { return _subtractionCounters; }
            set { _subtractionCounters = value; }
        }

        //private ObservableCollection<Shared.Counter> _customCounters;
        //public ObservableCollection<Shared.Counter> CustomCounters
        //{
        //    get { return _customCounters; }
        //    set { _customCounters = value; }
        //}

        private Shared.GameCard _currentPlane;
        public Shared.GameCard CurrentPlane
        {
            get { return _currentPlane; }
            set
            {
                if (_currentPlane != value)
                {
                    _currentPlane = value;
                    FirePropertyChanged("CurrentPlane");
                }
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void FirePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
        #endregion

        #region Constructors
        public GameModel(GamePresenter presenter)
        {
            InitializeModel(presenter);
            InitializeDataStore();
        }

        public GameModel(GamePresenter presenter, string dataDirectory)
        {
            InitializeModel(presenter);
            InitializeDataStore(dataDirectory);
        }

        private void InitializeModel(GamePresenter presenter)
        {
            _presenter = presenter;
            _playerDictionary = new Dictionary<int, PlayerState>();
            _allCards = new Dictionary<int, Faust.Andrew.MTG.Player.Shared.GameCard>();
            Hand = new ObservableCollection<Shared.GameCard>();
            Workspace = new ObservableCollection<Shared.GameCard>();
            Players = new ObservableCollection<PlayerState>();
            GraveYard = new ObservableCollection<Faust.Andrew.MTG.Player.Shared.GameCard>();
            Notifications = new ObservableCollection<Shared.Notification>();
            _tokens = new ObservableCollection<Token>();            
            LoadInitialCounters();
            LoadImages();
        }

        private Shared.Counter CreateCounter(Shared.CounterType counterType, int value, string name)
        {
            return CreateCounter(counterType, value, name, Colors.Transparent);
        }

        private Shared.Counter CreateCounter(Shared.CounterType counterType, int value, string name, Color color)
        {
            return new Shared.Counter() { CounterType = counterType, Value = value, Name = name, Color = color };
        }

        private void LoadInitialCounters()
        {            
            _additionCounters = new ObservableCollection<Faust.Andrew.MTG.Player.Shared.Counter>();            
            _subtractionCounters = new ObservableCollection<Faust.Andrew.MTG.Player.Shared.Counter>();
            //_customCounters = new ObservableCollection<Faust.Andrew.MTG.Player.Shared.Counter>();

            _additionCounters.Add(CreateCounter(Shared.CounterType.Power, 1, "Power", Color.FromArgb(0xC0, 0xFF, 0x00, 0x00)));
            _subtractionCounters.Add(CreateCounter(Shared.CounterType.Power, -1, "Power", Color.FromArgb(0xC0, 0xFF, 0x00, 0x00)));

            _additionCounters.Add(CreateCounter(Shared.CounterType.Toughness, 1, "Toughness", Color.FromArgb(0xC0, 0x00, 0x00, 0xFF)));
            _subtractionCounters.Add(CreateCounter(Shared.CounterType.Toughness, -1, "Toughness", Color.FromArgb(0xC0, 0x00, 0x00, 0xFF)));

            _additionCounters.Add(CreateCounter(Shared.CounterType.Other, 1, "Storage", Color.FromArgb(0x00, 0x00, 0x00, 0x00)));
            _subtractionCounters.Add(CreateCounter(Shared.CounterType.Other, -1, "Storage", Color.FromArgb(0x00, 0x00, 0x00, 0x00)));

            _additionCounters.Add(CreateCounter(Shared.CounterType.Other, 1, "Poison", Color.FromArgb(0xC0, 0x00, 0xFF, 0x00)));
            _subtractionCounters.Add(CreateCounter(Shared.CounterType.Other, -1, "Poison", Color.FromArgb(0xC0, 0x00, 0xFF, 0x00)));

            _additionCounters.Add(CreateCounter(Shared.CounterType.Other, 1, "Other 1", Color.FromArgb(0xE5, 0x00, 0xFF, 0xFF)));
            _subtractionCounters.Add(CreateCounter(Shared.CounterType.Other, -1, "Other 1", Color.FromArgb(0xE5, 0x00, 0xFF, 0xFF)));

            _additionCounters.Add(CreateCounter(Shared.CounterType.Other, 1, "Other 2", Color.FromArgb(0xE5, 0xFF, 0x00, 0xFF)));
            _subtractionCounters.Add(CreateCounter(Shared.CounterType.Other, -1, "Other 2", Color.FromArgb(0xE5, 0xFF, 0x00, 0xFF)));
        }

        protected void LoadImages()
        {
            _tokenImages = new Dictionary<string, ImageSource>();
            _tokenImages.Add("WhiteToken", Properties.Resources.WhiteToken.ToImageSource());
            _tokenImages.Add("BlackToken", Properties.Resources.BlackToken.ToImageSource());
            _tokenImages.Add("RedToken", Properties.Resources.RedToken.ToImageSource());
            _tokenImages.Add("GreenToken", Properties.Resources.GreenToken.ToImageSource());
            _tokenImages.Add("BlueToken", Properties.Resources.BlueToken.ToImageSource());
            _tokenImages.Add("BlackWhiteToken", Properties.Resources.BlackWhiteToken.ToImageSource());
            _tokenImages.Add("BlackRedToken", Properties.Resources.BlackRedToken.ToImageSource());
        }

        #endregion

        #region DataStore
        private void InitializeDataStore()
        {
            _userDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments) + "\\MTGManager";

            InitializeDataStore(_userDirectory);
        }

        private void InitializeDataStore(string dataDirectory)
        {            
            _dataStore = new DataStore(dataDirectory);
            _gatherer = new CachedGatherer(_dataStore);

            if (_dataStore.IsCurrentVersion == false)
            {
                _presenter.ReportError("Your database is out of date. Close the Player and launch the Manager to upgrade.");
            }
        }
        #endregion

        #region Game Joining/Starting
        public void HostGame()
        {
            string name = String.Empty;
            string deckName = String.Empty;
            int port = 0;
            bool usePlanes = false;

            List<string> decks = _dataStore.ListDecks();

            if (_presenter.GetHostParameters(decks, ref name, ref port, ref deckName, ref usePlanes))
            {
                IsUsingPlanes = usePlanes;
                _serverRunning = false;
                new Thread(new ParameterizedThreadStart(StartHost)).Start(port);

                while (!_serverRunning)
                {
                    Thread.Sleep(100);
                }

                JoinGame("localhost", port, name, _dataStore.GetDeck(deckName));
            }
        }

        private void StartHost(object port)
        {
            _server = new MTGServer(_isUsingPlanes);

            string address = String.Format("net.tcp://localhost:{0}", port);

            _serviceHost = new ServiceHost(_server, new Uri(address));

            NetTcpBinding binding = new NetTcpBinding();
            binding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.None;
            binding.Security.Mode = SecurityMode.None;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.None;

            _serviceHost.AddServiceEndpoint(typeof(Shared.IMTGServer), binding, address);

            _serviceHost.Open();

            _serverRunning = true;

            IsHosting = true;

            while (_serverRunning)
            {
                Thread.Sleep(100);
            }

            _serviceHost.Close();
            _serviceHost = null;
            _server = null;
        }                

        void Client_CommunicationsExceptionEvent(object sender, CommunicationExceptionEventArgs e)
        {
            _presenter.RaiseCommunicationsException(e);
        }

        public void JoinGame()
        {
            string name = String.Empty;
            string deckName = String.Empty;
            string host = String.Empty;
            bool isRejoin = false;
            int port = 0;

            List<string> decks = _dataStore.ListDecks();

            if (_presenter.GetJoinParameters(decks, ref name, ref host, ref port, ref deckName, ref isRejoin))
            {
                if (!isRejoin)
                {
                    JoinGame(host, port, name, _dataStore.GetDeck(deckName));
                }
                else
                {
                    RejoinGame(host, port, name);
                }
            }
        }

        private bool RejoinGame(string host, int port, string name)
        {
            InitializeClient(host, port);

            if (!_client.RejoinGame(name))
            {
                return false;
            }

            _client.StartClient(500);
            IsInGame = true;
            return true;
        }

        private bool JoinGame(string host, int port, string name, Deck deck)
        {            
            InitializeClient(host, port);

            if (!_client.JoinGame(name, deck))
            {
                return false;
            }

            _client.StartClient(500);
            IsInGame = true;
            return true;            
        }

        public void StartGame()
        {
            _server.StartGame();
        }

        public void EndGame()
        {
            _serverRunning = false;            
            IsHosting = false;
            IsInGame = false;
            IsGameStarted = false;
        }
        #endregion

        #region GameActions
        public void EndTurn()
        {
            _client.EndTurn(CurrentPlayer.PlayerID);
        }

        public void PlayTokenCreature(Token token, int quantity)
        {
            if (token == null)
                throw new ArgumentException("token is null.", "token");
            if (String.IsNullOrEmpty(token.ModifierText))
                throw new ArgumentException("ModiferText is null or empty.", "token");
            if (String.IsNullOrEmpty(token.ImageKey))
                throw new ArgumentException("ImageKey is null or empty.", "token");
            if (quantity < 1)
                throw new ArgumentException("quantity must be at least 1", "quantity");

            if (Tokens.Count
                (
                    tempToken => tempToken.ModifierText.Equals(token.ModifierText, StringComparison.CurrentCultureIgnoreCase)
                    && tempToken.ImageKey == token.ImageKey
                ) == 0)
            {
                //This token type hasn't been created yet so we add for later use
                Tokens.Add(token);
            }

            _client.PlayTokenCreature(CurrentPlayer.PlayerID, token.ModifierText, token.ImageKey, quantity);
        }

        public void AddCounter(Shared.GameCard card, Shared.Counter counter)
        {
            if (!CanControlCard(card)) { return; }

            Shared.Counter currentCounter = (from c in card.Counters
                                      where c.Name == counter.Name
                                      select c).SingleOrDefault();


            if (currentCounter == null)
            {
                Shared.Counter newCounter = (Shared.Counter)counter.Clone();
                card.Counters.Add(newCounter);
                UpdateCard(card);
            }
            else
            {
                currentCounter.Value += counter.Value;
                UpdateCard(card);
            }
        }

        public void AddCounter(Shared.GameCard card, Shared.CounterType counterType, string counterName, int counterChange)
        {
            AddCounter(card, new Shared.Counter() { Value = counterChange, Name = counterName, CounterType = counterType });
        }

        public void AddCounter(PlayerState player, Shared.Counter counter)
        {
            if (CurrentPlayer.PlayerID == player.PlayerID)
            {
                Shared.Counter currentCounter = (from c in player.Counters
                                                 where c.Name == counter.Name
                                                 select c).SingleOrDefault();


                if (currentCounter == null)
                {
                    Shared.Counter newCounter = (Shared.Counter)counter.Clone();
                    player.Counters.Add(newCounter);
                    UpdatePlayer(player);
                }
                else
                {
                    currentCounter.Value += counter.Value;
                    UpdatePlayer(player);
                }
            }
        }

        public Shared.GameCard DrawCard()
        {
            Shared.GameCard card = _client.DrawCard();

            if (card == null) { return null; }

            card = GetCurrentCard(card);

            return card;
        }

        public void DrawCard(bool sendToHand)
        {
            Shared.GameCard card = DrawCard();            

            if (card != null)
            {
                if (sendToHand)
                {
                    card.Location.Zone = Faust.Andrew.MTG.Player.Shared.Zone.Hand;
                }
                else
                {
                    card.Location.Zone = Faust.Andrew.MTG.Player.Shared.Zone.Workspace;
                }

                _client.MoveCard(card.CardID, card.Location);
            }
        }

        public void DrawCards(int count, bool sendToHand)
        {
            for (int i = 0; i < count; i++)
            {
                DrawCard(sendToHand);
            }
        }

        public bool CanControlCard(Shared.GameCard gameCard)
        {
            if (gameCard == null) { return false; }

            return CanControlCard(gameCard.CardID);
        }

        public bool CanControlCard(int cardID)
        {
            if (CurrentPlane != null && CurrentPlane.CardID == cardID) { return true; }

            return (_allCards[cardID].ControllerID == CurrentPlayer.PlayerID);
        }

        public bool CanMoveCard(Shared.GameCard gameCard)
        {
            if (gameCard == null) { return false; }

            return CanMoveCard(gameCard.CardID);
        }

        public bool CanMoveCard(int cardID)
        {
            //The player is allowed to move the card if they are the controller or it's in their play space.
            return (_allCards[cardID].ControllerID == CurrentPlayer.PlayerID 
                || _allCards[cardID].Location.PlayerID == CurrentPlayer.PlayerID);
        }

        public void ShowCards(List<Shared.GameCard> cards)
        {
            List<int> cardIds = new List<int>();
            foreach (Shared.GameCard card in cards)
            {
                if (card.ControllerID == CurrentPlayer.PlayerID)
                {
                    cardIds.Add(card.CardID);
                }
            }

            List<int> playerIds = null;
            if (cardIds.Count > 0)
            {
                playerIds = _presenter.GetTargetPlayers();
            }

            if (playerIds != null && playerIds.Count > 0)
            {
                _client.ShowCards(playerIds, cardIds);
            }
        }        

        public int RollDie(int sides)
        {
            _presenter.PlayDieRollSound();
            return _client.RollDie(CurrentPlayer.PlayerID, sides);
        }

        public bool MoveCard(PlayerState sourcePlayer, PlayerState targetPlayer, Faust.Andrew.MTG.Player.Shared.GameCard card)
        {
            if (card == null || sourcePlayer == null) { return false; }
            if (card.ControllerID != CurrentPlayer.PlayerID) { return false; }
            if (targetPlayer == null || targetPlayer == sourcePlayer) { return false; }

            
            _playerDictionary[card.Location.PlayerID].Cards.Remove(card);
            card.Location.PlayerID = targetPlayer.PlayerID;

            if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.InPlay)
            {
                //We only want to put the card on the table immediately if it happens to 
                //already be on the table. If it's coming from anywhere else (such as the deck)
                //then the server needs to initiate the move.
                targetPlayer.Cards.Add(card);
            }

            card.Location.Zone = Faust.Andrew.MTG.Player.Shared.Zone.InPlay;            
            
            return _client.MoveCard(card.CardID, card.Location);
        }

        public bool MoveCard(Shared.GameCard card)
        {
            //if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.InPlay)
            {
                if (card.Location.X < 0)
                {
                    card.Location.X = 0;
                }

                if (card.Location.Y < 0)
                {
                    card.Location.Y = 0;
                }

                if (card.Location.X > _presenter.MeasurePlayArea(card.Location.PlayerID).X)
                {
                    card.Location.X = _presenter.MeasurePlayArea(card.Location.PlayerID).X - _presenter.MeasureCard(card.CardID).X;
                }

                if (card.Location.Y > _presenter.MeasurePlayArea(card.Location.PlayerID).Y)
                {
                    card.Location.Y = _presenter.MeasurePlayArea(card.Location.PlayerID).Y - _presenter.MeasureCard(card.CardID).Y;
                }

                return _client.MoveCard(card.CardID, card.Location);
            }
            //else
            //{
            //    return false;
            //}
        }

        public bool PlayCard(Shared.GameCard card)
        {
            //Only the card's controller can play it.
            if (card.ControllerID != CurrentPlayer.PlayerID) { return false; }

            Shared.GameCard currentCard = GetCurrentCard(card);
            
            currentCard.Location.Zone = Faust.Andrew.MTG.Player.Shared.Zone.InPlay;
            currentCard.Location.X = card.Location.X;
            currentCard.Location.Y = card.Location.Y;
            currentCard.Location.Z = card.Location.Z;
            currentCard.Location.PlayerID = card.Location.PlayerID;
            currentCard.PropertyChanged += Card_PropertyChanged;

            UpdateCard(currentCard);

            return _client.MoveCard(currentCard.CardID, currentCard.Location);
        }

        public bool CanUpdatePlayer(Shared.Player player)
        {
            return player.PlayerID == CurrentPlayer.PlayerID;
        }

        public bool UpdateCard(Shared.GameCard card)
        {
            return _client.UpdateCard(card);
        }        

        private void UpdatePlayer(Shared.Player player)
        {
            Shared.Player basePlayer = player;

            if (player is PlayerState)
            {
                basePlayer = ((PlayerState)player).GetBase();
            }

            _client.UpdatePlayer(basePlayer);
        }

        public void SendToZone(Shared.GameCard gameCard, Faust.Andrew.MTG.Player.Shared.Zone zone)
        {
            if (!CanControlCard(gameCard)) { return; }

            Shared.Location location = new Faust.Andrew.MTG.Player.Shared.Location()
            {
                PlayerID = gameCard.OwnerID,
                Zone = zone
            };

            _client.MoveCard(gameCard.CardID, location);
        }

        public void SendToHand(Shared.GameCard gameCard)
        {
            SendToZone(gameCard, Faust.Andrew.MTG.Player.Shared.Zone.Hand);
        }

        public void SendToHand(List<Shared.GameCard> cards)
        {
            foreach (Shared.GameCard card in cards)
            {
                SendToZone(card, Faust.Andrew.MTG.Player.Shared.Zone.Hand);
            }
        }

        public void Mulligan()
        {
            int handCount = 0;
            while (Hand.Count > 0)
            {
                handCount++;
                SendToDeck(Hand[0], Faust.Andrew.MTG.Player.Shared.DeckPosition.Top, 0);
            }

            ShuffleDeck(CurrentPlayer.PlayerID);

            for (int i = 0; i < (handCount - 1); i++)
            {
                DrawCard(true);
            }
        }

        public void UntapAll()
        {
            var cards = from card in _allCards.Values
                        where card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.InPlay
                        && CanControlCard(card.CardID)
                        && !(card.State == Faust.Andrew.MTG.Player.Shared.CardState.Normal)
                        select card;

            foreach (Shared.GameCard card in cards)
            {
                card.State = Faust.Andrew.MTG.Player.Shared.CardState.Normal;                
            }
        }

        public void ToggleTap(Shared.GameCard card)
        {
            if (CanControlCard(card.CardID))
            {
                if (card.State == Faust.Andrew.MTG.Player.Shared.CardState.Normal)
                {
                    card.State = Faust.Andrew.MTG.Player.Shared.CardState.Tapped;
                }
                else
                {
                    card.State = Faust.Andrew.MTG.Player.Shared.CardState.Normal;
                }
            }
        }

        public void SendToDeck(List<Shared.GameCard> cards, Shared.DeckPosition deckPosition, int offset)
        {
            foreach (Shared.GameCard card in cards)
            {
                SendToDeck(card, deckPosition, offset);
            }
        }

        public void SendToDeck(Shared.GameCard gameCard, Shared.DeckPosition deckPosition, int offset)
        {
            if (!CanControlCard(gameCard)) { return; }

            RemoveCardFromZone(gameCard);

            _client.SendCardToDeck(CurrentPlayer.PlayerID, gameCard.CardID, deckPosition, offset);
        }

        public void SendToGraveyard(List<Shared.GameCard> cards)
        {
            foreach (Shared.GameCard card in cards)
            {
                SendToGraveyard(card);
            }
        }

        public void SendToGraveyard(Shared.GameCard gameCard)
        {
            //If it's a token card we will use the RemoveCard instead.
            if (gameCard.IsToken)
            {
                RemoveCard(gameCard);
                return;
            }
            else
            {
                SendToZone(gameCard, Faust.Andrew.MTG.Player.Shared.Zone.Graveyard);
            }
        }

        public void RemoveCard(Shared.GameCard gameCard)
        {
            if (gameCard.IsToken && CurrentPlayer.PlayerID == gameCard.ControllerID)
            {
                _client.RemoveTokenCreature(CurrentPlayer.PlayerID, gameCard.CardID);
            }
        }

        public void SendToWorkspace(List<Shared.GameCard> cards)
        {
            foreach (Shared.GameCard card in cards)
            {
                SendToZone(card, Faust.Andrew.MTG.Player.Shared.Zone.Workspace);
            }
        }

        public void SendToWorkspace(Shared.GameCard gameCard)
        {
            SendToZone(gameCard, Faust.Andrew.MTG.Player.Shared.Zone.Workspace);
        }

        private void RemoveCardFromZone(Shared.GameCard card)
        {
            Hand.Remove(card);
            Workspace.Remove(card);
            GraveYard.Remove(card);
            foreach (PlayerState player in _players)
            {
                player.Cards.Remove(card);
            }
        }

        public bool ShuffleDeck(int playerID)
        {
            if (playerID == CurrentPlayer.PlayerID)
            {
                _presenter.PlayShuffleSound();
                return _client.ShuffleDeck(playerID);
            }
            else
            {
                return false;
            }
        }

        public void AttempPlaneswalk()
        {
            _presenter.PlayDieRollSound();
            _client.AttemptPlaneswalk();                
        }

        public ObservableCollection<Shared.GameCard> GetDeck(int playerID)
        {
            List<Shared.GameCard> cards = _client.GetCardList(CurrentPlayer.PlayerID, playerID, Faust.Andrew.MTG.Player.Shared.Zone.Deck);
            //_client.Notify("Is viewing his/her deck");
            return new ObservableCollection<Faust.Andrew.MTG.Player.Shared.GameCard>(cards);
        }        

        public ObservableCollection<Shared.GameCard> GetGraveyard(int playerID)
        {
            List<Shared.GameCard> cards = _client.GetCardList(CurrentPlayer.PlayerID, playerID, Faust.Andrew.MTG.Player.Shared.Zone.Graveyard);
            return new ObservableCollection<Faust.Andrew.MTG.Player.Shared.GameCard>(cards);
        }

        public bool TransferControl(Shared.GameCard card)
        {            
            if (card.Location.Zone != Faust.Andrew.MTG.Player.Shared.Zone.InPlay || !CanControlCard(card))
            {
                return false;
            }            

            int targetPlayer = _presenter.GetTargetPlayer();

            if (targetPlayer < 0)
            {
                return false;
            }

            return _client.TransferCard(targetPlayer, card.CardID);
        }

        void Card_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (_ignoreCardPropertyChange) { return; }

            if (e.PropertyName == "State")
            {
                UpdateCard(sender as Shared.GameCard);
            }
            else if (e.PropertyName == "IsFaceDown")
            {
                UpdateCard(sender as Shared.GameCard);
            }
            else if (e.PropertyName == "Counters")
            {
                UpdateCard(sender as Shared.GameCard);
            }
        }

        void Player_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (_ignorePlayerPropertyChange) { return; }

            if (e.PropertyName == "LifeCounter")
            {
                UpdatePlayer(sender as Shared.Player);
            }
            else if (e.PropertyName == "Counters")
            {
                UpdatePlayer(sender as Shared.Player);
            }
            else if (e.PropertyName == "IsDeckUnlocked")
            {
                UpdatePlayer(sender as Shared.Player);
            }
        }

        public void RandomReveal()
        {
            RandomCardParameters cardParams = _presenter.GetRandomRevealParameters();
            if (cardParams != null)
            {
                _server.RandomReveal(CurrentPlayer.PlayerID, cardParams.Count, cardParams.SourceZone, cardParams.TargetPlayers);
            }
        }

        public void RandomDiscard()
        {
            RandomCardParameters cardParams = _presenter.GetRandomDiscardParameters();
            if (cardParams != null)
            {
                _server.RandomDiscard(CurrentPlayer.PlayerID, cardParams.Count, cardParams.SourceZone);
            }
        }

        public bool SendMessage(string message, List<int> targetPlayers)
        {
            //If there is no message or targets to send the message to, we return false.
            if (String.IsNullOrEmpty(message) || targetPlayers.Count == 0) { return false; }

            //If there is only one message target, and it happens to be the current player we return false.
            if (targetPlayers.Count == 1 && targetPlayers[0] == CurrentPlayer.PlayerID) { return false; }

            //We have a valid message and targets so we send it.
            _client.SendMessage(targetPlayers, message);

            return true;
        }
        #endregion

        #region GameEvents

        private void InitializeClient(string host, int port)
        {
            _client = new Client(host, port, _gatherer, _dataStore);
            _client.CommunicationsExceptionEvent += new CommunicationsExceptionHandler(Client_CommunicationsExceptionEvent);
            //_client.GameStartEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.GameStartAction>(GameStartEvent);
            //_client.MarkCardEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.MarkCardAction>(MarkCardEvent);
            //_client.MessageEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.MessageAction>(MessageEvent);
            _client.EndTurnEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.EndTurnAction>(EndTurnEvent);
            _client.MoveCardEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.MoveCardAction>(MoveCardEvent);
            _client.NotificationEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.NotificationAction>(NotificationEvent);
            _client.RefreshGameStateEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.RefreshGameStateAction>(RefreshGameStateEvent);
            _client.ShowCardsEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.ShowCardsAction>(ShowCardsEvent);
            _client.TransferControlEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.TransferControlAction>(TransferControlEvent);
            _client.UpdateCardEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.UpdateCardAction>(UpdateCardEvent);
            _client.UpdatePlayerEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.UpdatePlayerAction>(UpdatePlayerEvent);
            _client.RemoveTokenEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.RemoveTokenAction>(RemoveTokenEvent);
            _client.PlanesWalkEvent += new ActionEventHandler<Faust.Andrew.MTG.Player.Shared.PlanesWalkAction>(PlanesWalkEvent);
        }

        void PlanesWalkEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.PlanesWalkAction> e)
        {
            Shared.GameCard card = _allCards[e.Action.CardID];
            CurrentPlane = card;
        }

        void EndTurnEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.EndTurnAction> e)
        {
            _presenter.PlayNewTurnSound();
            foreach (PlayerState player in _players)
            {
                player.IsTurn = (player.PlayerID == e.Action.TargetPlayerID);
            }
        }

        void TransferControlEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.TransferControlAction> e)
        {
            Shared.GameCard card = _allCards[e.Action.CardID];
                       
            card.ControllerID = e.Action.TargetPlayerID;           
        }

        void NotificationEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.NotificationAction> e)
        {
            //StringBuilder message = new StringBuilder();            

            Notifications.Insert(0, e.Action.Notification);
            //Notifications.Add(e.Action.Notification);

            //_presenter.ShowNotifications();
        }

        void ShowCardsEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.ShowCardsAction> e)
        {
            ObservableCollection<Shared.GameCard> cards = new ObservableCollection<Faust.Andrew.MTG.Player.Shared.GameCard>();

            foreach (Shared.GameCard card in e.Action.Cards)
            {
                Shared.GameCard currentCard = GetCurrentCard(card);
                cards.Add(currentCard);
            }

            _presenter.ViewCards(cards);
        }

        void UpdateCardEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.UpdateCardAction> e)
        {
            Shared.GameCard card = _allCards[e.Action.CardID];
            Shared.GameCard updatedCard = e.Action.Card;

            _ignoreCardPropertyChange = true;
            
            card.State = updatedCard.State;                        
            card.Counters = updatedCard.Counters;
            card.ModifierText = updatedCard.ModifierText;
            card.IsFaceDown = updatedCard.IsFaceDown;
            card.ControllerID = updatedCard.ControllerID;

            _ignoreCardPropertyChange = false;
        }

        void RefreshGameStateEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.RefreshGameStateAction> e)
        {
            System.Diagnostics.Trace.WriteLine("GameEvent: Refresh Game State");
            //_gameState = e.Action.GameState;

            _presenter.StartingGameLoading();
            ObservableCollection<PlayerState> players = new ObservableCollection<PlayerState>();
            Hand.Clear();
            Workspace.Clear();
            _playerDictionary.Clear();
            _allCards.Clear();
            IsUsingPlanes = e.Action.GameState.IsPlanechase;

            //_gameState = e.Action.GameState;
            List<PlayerState> tempPlayers = new List<PlayerState>();
            foreach (Shared.Player player in e.Action.GameState.Players)
            {
                PlayerState playerState = new PlayerState(player);

                //players.Add(playerState);
                tempPlayers.Add(playerState);

                _playerDictionary.Add(player.PlayerID, playerState);
                if (player.PlayerID == _client.PlayerID)
                {
                    CurrentPlayer = playerState;
                }

                playerState.PropertyChanged += Player_PropertyChanged;
            }

            foreach (PlayerState tempPlayer in tempPlayers.OrderBy(p => p.Order))
            {
                players.Add(tempPlayer);
            }

            int count = 0;
            int totalCount = e.Action.GameState.Cards.Count;
            //Now we need to load the cards
            foreach (Shared.GameCard card in e.Action.GameState.Cards)
            {
                count++;
                _presenter.ReportGameLoadProgress(count, totalCount);

                //Load the card
                if (!card.IsToken)
                {
                    _gatherer.LoadCardDetails(card);
                }

                _allCards.Add(card.CardID, card);
                card.PropertyChanged += Card_PropertyChanged;

                //Now assign it to the proper player
                if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.InPlay)
                {
                    _playerDictionary[card.ControllerID].Cards.Add(card);
                }
                else
                {
                    if (card.ControllerID == _client.PlayerID)
                    {
                        if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Hand)
                        {
                            Hand.Add(card);
                        }
                        else if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Workspace)
                        {
                            Workspace.Add(card);
                        }
                    }
                }
            }

            Players = players;
            IsGameStarted = true;
            
            if (IsUsingPlanes && e.Action.GameState.CurrentPlane != null)
            {
                CurrentPlane = _allCards[e.Action.GameState.CurrentPlane.CardID];
            }

            _presenter.FinishedGameLoading();
            _presenter.RefreshGameState();
        }

        void RemoveTokenEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.RemoveTokenAction> e)
        {
            Shared.GameCard card = _allCards[e.Action.CardID];

            PlayerState player = _playerDictionary[card.Location.PlayerID];

            player.Cards.Remove(card);
        }

        private Shared.GameCard GetCurrentCard(Shared.GameCard card)
        {
            Shared.GameCard currentCard;
            if (!_allCards.ContainsKey(card.CardID))
            {
                if (!card.IsToken)
                {
                    _gatherer.LoadCardDetails(card);
                }
                else
                {
                    if (_tokenImages.ContainsKey(card.TokenImageKey))
                    {
                        card.SetImage(_tokenImages[card.TokenImageKey]);
                    }
                    else
                    {
                        card.SetImage(_tokenImages["BlackToken"]);
                    }
                }

                _allCards.Add(card.CardID, card);
                currentCard = card;
                currentCard.PropertyChanged += Card_PropertyChanged;
            }
            else
            {
                currentCard = _allCards[card.CardID];
            }
            return currentCard;
        }

        void MoveCardEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.MoveCardAction> e)
        {
            Shared.GameCard card = e.Action.Card;
            Shared.GameCard currentCard;

            currentCard = GetCurrentCard(card);

            RemoveCardFromZone(currentCard);

            //get the player to update.
            PlayerState playerToUpdate = _playerDictionary[card.Location.PlayerID];

            //Set the new Location information
            currentCard.Location.PlayerID = card.Location.PlayerID;
            currentCard.Location.X = card.Location.X;
            currentCard.Location.Y = card.Location.Y;
            currentCard.Location.Z = card.Location.Z;

            if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.InPlay)
            {

                currentCard.Location.Zone = Faust.Andrew.MTG.Player.Shared.Zone.InPlay;

                //If the card isn't already in the play are then we need to add it.
                if (!playerToUpdate.Cards.Contains(currentCard))
                {
                    playerToUpdate.Cards.Add(currentCard);
                }
            }
            else if (card.OwnerID == CurrentPlayer.PlayerID)
            {
                currentCard.Location.Zone = card.Location.Zone;
                if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Hand)
                {
                    Hand.Add(currentCard);
                }
                else if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Workspace)
                {
                    Workspace.Add(currentCard);
                }
                else if (card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Graveyard)
                {
                    GraveYard.Add(currentCard);
                }
            }
        }

        void UpdatePlayerEvent(object sender, ActionEventArgs<Faust.Andrew.MTG.Player.Shared.UpdatePlayerAction> e)
        {
            Shared.Player player = e.Action.Player;

            _ignorePlayerPropertyChange = true;

            PlayerState playerState = _playerDictionary[player.PlayerID];
            playerState.HandCount = player.HandCount;
            playerState.DeckCount = player.DeckCount;
            playerState.GraveyardCount = player.GraveyardCount;
            playerState.Counters = player.Counters;            
            playerState.LifeCounter = player.LifeCounter;
            playerState.IsDeckUnlocked = player.IsDeckUnlocked;

            _ignorePlayerPropertyChange = false;
        }
        #endregion
    }
}